package com.yupi.springbootinit.mq.mqttproxy;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.msgpack.core.MessagePack;
import org.msgpack.value.MapValue;
import org.msgpack.value.Value;

import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * 厂家 MQTT(JSON/MessagePack) → JSON 代理
 */
public class MqttProxyService {
    private final String brokerUrl;
    private final String printerId;
    private final String version;
    private MqttClient client;

    private static final ObjectMapper mapper = new ObjectMapper();

    private static final Set<String> ALLOWED_FIELDS = Set.of(
            "oxygen", "cabinTemp", "filterDp", "ductTemp",
            "powderUsage", "printStart", "printOver",
            "totalAccount", "remainingAccount",
            "totalHeight", "remainingHeight",
            "estimatedTime", "remainingTime",
            "machineStatus"
    );

    public MqttProxyService(String brokerUrl, String printerId, String version) {
        this.brokerUrl = brokerUrl;
        this.printerId = printerId;
        this.version = version;
    }

    /**
     * 启动 MQTT Proxy 服务（带超时和自动重连）
     */
    public void start() throws Exception {
        // 创建客户端
        client = new MqttClient(brokerUrl, "mqtt-proxy-" + printerId);

        // 配置连接选项
        MqttConnectOptions options = new MqttConnectOptions();
        options.setConnectionTimeout(10);       // 连接超时 10 秒
        options.setKeepAliveInterval(60);       // 心跳间隔 60 秒
        options.setAutomaticReconnect(true);    // 断线自动重连
        options.setCleanSession(true);          // 清除会话
        options.setMaxInflight(100);            // 最大并发消息数

        // 连接 Broker
        client.connect(options);
        System.out.println("[Proxy] ✅ 已连接到 Broker: " + brokerUrl + " | PrinterId: " + printerId);

        // 订阅厂家 4 个关键 Topic
        subscribeAndForward("Status");
        subscribeAndForward("Task/Current");
        subscribeAndForward("Task/Remainingtime");
        subscribeAndForward("Atmosphere/Current");

        // 定时发送 GET 拉取
        schedulePull("Status", 20_000);
        schedulePull("Task/Current", 15_000);
        schedulePull("Task/Remainingtime", 30_000);
        schedulePull("Atmosphere/Current", 10_000);

        System.out.println("[Proxy] ✅ 启动完成: " + printerId + "，等待消息...");
    }

    /**
     * 订阅厂家Topic → 解码 → 转发 /flat
     */
    private void subscribeAndForward(String subPath) throws Exception {
        String topic = version + "/Printers/" + printerId + "/" + subPath;
        String forwardTopic = topic + "/flat";

        client.subscribe(topic, (receivedTopic, msg) -> {
            try {
                byte[] payload = msg.getPayload();

                Map<String, Object> flat = new LinkedHashMap<>();

                if (looksLikeJson(payload)) {
                    Object root = mapper.readValue(payload, Object.class);
                    flattenJson(subPath, null, root, flat);
                } else {
                    Value v = MessagePack.newDefaultUnpacker(payload).unpackValue();
                    flattenMsgpack(subPath, null, v, flat);
                }

                deriveFields(flat);
                Map<String, Object> out = filterAllowed(flat);

                if (out.isEmpty()) {
                    return;
                }

                String json = mapper.writeValueAsString(out);
                client.publish(forwardTopic, new MqttMessage(json.getBytes(StandardCharsets.UTF_8)));

            } catch (Exception e) {
                System.err.println("[Proxy] ❌ 转发失败: " + e.getMessage());
                e.printStackTrace();
            }
        });
    }

    /**
     * 定时发送 GET 拉取设备数据
     */
    private void schedulePull(String subPath, long periodMs) {
        String getTopic = "get/" + version + "/Printers/" + printerId + "/" + subPath;
        Timer timer = new Timer(true);
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                try {
                    if (client != null && client.isConnected()) {
                        client.publish(getTopic, new MqttMessage(new byte[0]));
                    }
                } catch (Exception e) {
                    System.err.println("[Proxy] GET 请求失败: " + e.getMessage());
                }
            }
        }, 2000, periodMs);
    }

    // ========================== JSON 展开 ==========================

    private void flattenJson(String ctx, String prefix, Object node, Map<String, Object> out) {
        if (node == null) return;

        if (node instanceof Map) {
            @SuppressWarnings("unchecked")
            Map<Object, Object> mv = (Map<Object, Object>) node;

            Object direct = findValueField(mv);
            if (direct != null && prefix != null && !prefix.isEmpty()) {
                String renamed = renameKey(ctx, prefix);
                if (renamed != null) out.put(renamed, normalizeJsonScalar(direct));
            }

            for (Map.Entry<Object, Object> e : mv.entrySet()) {
                String k = String.valueOf(e.getKey());
                Object v = e.getValue();
                String next = joinKey(prefix, k);

                if (v instanceof Map) {
                    Object inner = findValueField((Map<?, ?>) v);
                    if (inner != null) {
                        String renamed = renameKey(ctx, next);
                        if (renamed != null) out.put(renamed, normalizeJsonScalar(inner));
                    }
                }
                flattenJson(ctx, next, v, out);
            }
            return;
        }

        if (node instanceof List) {
            List<?> arr = (List<?>) node;
            for (int i = 0; i < arr.size(); i++) {
                flattenJson(ctx, joinKey(prefix, String.valueOf(i)), arr.get(i), out);
            }
            return;
        }

        String renamed = renameKey(ctx, prefix);
        if (renamed != null) {
            out.put(renamed, normalizeJsonScalar(node));
        }
    }

    private Object findValueField(Map<?, ?> mv) {
        for (Map.Entry<?, ?> e : mv.entrySet()) {
            Object k = e.getKey();
            if (k != null) {
                String s = String.valueOf(k);
                if ("value".equalsIgnoreCase(s)) return e.getValue();
            }
        }
        return null;
    }

    private Object normalizeJsonScalar(Object v) {
        if (v == null) return null;
        if (v instanceof Number) return v;
        if (v instanceof Boolean) return v;
        if (v instanceof String) return v;
        return String.valueOf(v);
    }

    // ========================== MsgPack 展开 ==========================

    private void flattenMsgpack(String ctx, String prefix, Value value, Map<String, Object> out) {
        if (value == null) return;

        if (value.isMapValue()) {
            MapValue mv = value.asMapValue();

            Value direct = findValueField(mv);
            if (direct != null && prefix != null && !prefix.isEmpty()) {
                String renamed = renameKey(ctx, prefix);
                if (renamed != null) out.put(renamed, parseMsgpackScalar(direct));
            }

            mv.map().forEach((k, v) -> {
                String key = toKeyString(k);
                String next = joinKey(prefix, key);

                if (v.isMapValue()) {
                    Value inner = findValueField(v.asMapValue());
                    if (inner != null) {
                        String renamed = renameKey(ctx, next);
                        if (renamed != null) out.put(renamed, parseMsgpackScalar(inner));
                    }
                }
                flattenMsgpack(ctx, next, v, out);
            });
            return;
        }

        if (value.isArrayValue()) {
            var arr = value.asArrayValue();
            for (int i = 0; i < arr.size(); i++) {
                flattenMsgpack(ctx, joinKey(prefix, String.valueOf(i)), arr.get(i), out);
            }
            return;
        }

        String renamed = renameKey(ctx, prefix);
        if (renamed != null) {
            out.put(renamed, parseMsgpackScalar(value));
        }
    }

    private Value findValueField(MapValue mv) {
        for (var e : mv.map().entrySet()) {
            Value k = e.getKey();
            if (k.isStringValue()) {
                String s = k.asStringValue().asString();
                if ("value".equalsIgnoreCase(s)) return e.getValue();
            }
        }
        return null;
    }

    private String toKeyString(Value k) {
        if (k.isStringValue()) return k.asStringValue().asString();
        if (k.isIntegerValue()) return String.valueOf(k.asIntegerValue().toLong());
        if (k.isFloatValue()) return String.valueOf(k.asFloatValue().toDouble());
        return k.toString();
    }

    private Object parseMsgpackScalar(Value v) {
        if (v.isNilValue()) return null;
        if (v.isIntegerValue()) return v.asIntegerValue().toLong();
        if (v.isFloatValue()) return v.asFloatValue().toDouble();
        if (v.isBooleanValue()) return v.asBooleanValue().getBoolean();
        if (v.isStringValue()) return v.asStringValue().asString();
        if (v.isBinaryValue()) {
            byte[] b = v.asBinaryValue().asByteArray();
            try {
                return new String(b, StandardCharsets.UTF_8);
            } catch (Exception ignore) {
                return Base64.getEncoder().encodeToString(b);
            }
        }
        return v.toString();
    }

    // ========================== 映射与过滤 ==========================

    private String renameKey(String ctx, String key) {
        if (key == null) return null;
        String k = key.replace('/', '_');
        String lower = k.toLowerCase(Locale.ROOT);
        String ctxLower = ctx == null ? "" : ctx.toLowerCase(Locale.ROOT);

        if ("status".equals(ctxLower) && (lower.equals("status") || lower.endsWith("_status"))) {
            return "machineStatus";
        }

        if (lower.endsWith("totallayer")) return "totalAccount";
        if (lower.endsWith("layerindex")) return "currentLayer";
        if (lower.endsWith("thickness")) return "layerThickness";
        if (lower.endsWith("times_begin") || lower.endsWith("begin")) return "printStart";
        if (lower.endsWith("times_remaining") || lower.endsWith("remaining") || lower.contains("remainingtime"))
            return "remainingTime";

        if (lower.contains("oxygen")) return "oxygen";
        if (lower.contains("environmenttemperature")) return "cabinTemp";
        if (lower.contains("filterpressurediff")) return "filterDp";
        if (lower.contains("fanpipetemp") || lower.contains("fanpipetemperature")) return "ductTemp";

        return null;
    }

    private void deriveFields(Map<String, Object> m) {
        Long totalAccount = asLong(m.get("totalAccount"));
        Long currentLayer = asLong(m.get("currentLayer"));
        if (totalAccount != null && currentLayer != null) {
            long ra = Math.max(0, totalAccount - currentLayer);
            m.put("remainingAccount", ra);
        }

        Double thickness = asDouble(m.get("layerThickness"));
        if (thickness != null && totalAccount != null) {
            m.put("totalHeight", thickness * totalAccount);
        }
        Long remainingAccount = asLong(m.get("remainingAccount"));
        if (thickness != null && remainingAccount != null) {
            m.put("remainingHeight", thickness * remainingAccount);
        }

        Long printStart = asLong(m.get("printStart"));
        Long remainingTime = asLong(m.get("remainingTime"));
        if (printStart != null && printStart > 0 && remainingTime != null) {
            long now = System.currentTimeMillis() / 1000;
            long est = Math.max(0, (now - printStart) + remainingTime);
            m.put("estimatedTime", est);
        }
    }

    private Map<String, Object> filterAllowed(Map<String, Object> m) {
        Map<String, Object> out = new LinkedHashMap<>();
        for (var e : m.entrySet()) {
            String k = e.getKey();
            Object v = e.getValue();
            if (ALLOWED_FIELDS.contains(k) && v != null) {
                out.put(k, v);
            }
        }
        return out;
    }

    private Long asLong(Object o) {
        if (o == null) return null;
        if (o instanceof Number) return ((Number) o).longValue();
        try {
            return Long.parseLong(String.valueOf(o));
        } catch (Exception ignore) {
            return null;
        }
    }

    private Double asDouble(Object o) {
        if (o == null) return null;
        if (o instanceof Number) return ((Number) o).doubleValue();
        try {
            return Double.parseDouble(String.valueOf(o));
        } catch (Exception ignore) {
            return null;
        }
    }

    // ========================== 工具 ==========================

    private boolean looksLikeJson(byte[] payload) {
        if (payload == null || payload.length == 0) return false;
        int i = 0;
        while (i < payload.length && Character.isWhitespace(payload[i])) i++;
        if (i >= payload.length) return false;
        byte b = payload[i];
        return b == '{' || b == '[';
    }

    private String joinKey(String prefix, String subKey) {
        if (subKey == null || subKey.isEmpty()) return prefix;
        if (prefix == null || prefix.isEmpty()) return subKey;
        return prefix + "_" + subKey;
    }
}